//use std::cell::UnsafeCell;
use std::ops::Deref;
use std::io::IoSlice;
use std::sync::Arc;
use super::container::item::ContainerWriteItem;
use super::chunk_meta::ChunkLayout;

use crate::chunk_meta::{CHUNK_LAYOUT_NONE, ChunkKind};
use crate::rechunk::ReChunk;
use crate::constants::DefaultFingerprintType;
use crate::container::{ContainerId, ContainerStore};
use crate::recipe::{ChunkPointer, FpPointer};
use crate::superfeature::SuperFeature;
use crate::xcontainer::DynContainerWriteItem;
use util::{Cp,WriteOnce};

pub enum BaseChunk {
	Recent(Arc<Chunk>),
	Store(Arc<ReChunk>),
}

impl BaseChunk {
	pub fn data(&self) -> &[u8] {
		match self {
			Self::Recent(c) => &c,
			Self::Store(c) => &c,
		}
	}

	pub fn fp(&self) -> &DefaultFingerprintType {
		match self {
			Self::Recent(c) => c.fp(),
			Self::Store(c) => c.fp(),
		}
	}
}

pub struct Chunk {
	data:Cp<[u8]>,
	layout:WriteOnce<ChunkLayout>,
	id_no:Arc<WriteOnce<(ContainerId,u32)>>,
	sf:Arc<WriteOnce<SuperFeature>>,
	base_pointer:WriteOnce<Option<Box<FpPointer>>>,
	base_chunk:WriteOnce<Option<BaseChunk>>,
	delta:WriteOnce<Option<Vec<u8>>>,
}

impl Chunk {
	pub fn new(cp:Cp<[u8]>,fp:DefaultFingerprintType) -> Self {
		Self {
			layout:WriteOnce::new_init(ChunkLayout::unique(cp.len(),fp)),
			data:cp,
			id_no:WriteOnce::new_uninit_arc(),
			sf:WriteOnce::new_uninit_arc(),
			base_pointer:WriteOnce::new(None),
			base_chunk:WriteOnce::new(None),
			delta:WriteOnce::new(None),
		}
	}
	
	pub fn fp(&self) -> &DefaultFingerprintType {
		&self.layout.read().fp()
	}

	pub fn layout(&self) -> &ChunkLayout {
		&self.layout.read()
	}

	pub fn sf(&self) -> &SuperFeature {
		self.sf.read()
	}

	pub fn sf_init(&self) {
		self.sf.write(|sf|{
			sf.init(&self.data);
		})
	}

	pub fn set_base_chunk_pointer(&self,pointer:Option<Box<FpPointer>>) {
		pointer.map(|p|self.base_pointer.write(|pointer|*pointer = Some(p.clone())));
	}

	pub fn set_chunk_base(&self,c:&Arc<Chunk>) {
		self.base_chunk.write(|base|*base = Some(BaseChunk::Recent(c.clone())));
	}

	pub fn set_rechunk_base(&self,c:&Arc<ReChunk>) {
		if !c.is_unique() {
			println!("base chunk {:?} should be unique",c.fp());
			assert!(c.is_unique());
		}
		self.base_chunk.write(|base|*base = Some(BaseChunk::Store(c.clone())));
	}

	pub fn set_base(&self,store:&ContainerStore) {
		if !self.base_pointer.is_init() { return; }
		if let Some(cp) = self.base_pointer.read() {
			if let Some(base_chunk) = store.find_chunk_in_write_buf(cp.id(),cp.fp()){
				self.set_chunk_base(&base_chunk);
			} else if let Ok(base_chunk) = cp.read_exact(&store) {
				self.set_rechunk_base(&base_chunk);
			} else {
				panic!("hanging chunk pointer {:?}",cp);
			}
		}
	}

	pub fn record_self_and_base(&self) -> (usize, usize) {
		if !self.base_chunk.is_init() { return (0, 0); }
		if let Some(base) = self.base_chunk.read() {
			return (self.data.len(), base.data().len());
		} else {
			return (0, 0);
		};
	}

	pub fn delta(&self) {
		if !self.base_chunk.is_init() { return; }
		let if_delta_max_len = self.if_delta_max_len();
		if let Some(base) = self.base_chunk.read() {
			self.delta.write(|vec|{
				*vec = xdelta3::encode(&self.data, base.data(),if_delta_max_len);
		});
		} else {
			return;
		}
		if let Some(delta) = self.delta.read() {
			let delta_len = delta.len();
			//let target = xdelta3::decode(delta, self.base.read().as_ref().unwrap().data());
			//assert!(target.is_some());
			//assert_eq!(&target.unwrap() as &[u8],&self.data as &[u8]);
			if self.as_if_delta_len(delta_len) < self.as_unique_len() {
				unsafe {
					self.layout.refresh();
					self.layout.write(|layout|layout.set_delta_len(delta_len));
				}
			}
		}
	}

	pub fn set_duplicate(&mut self,chunk:&Chunk) {
		self.id_no = chunk.id_no_pointer();
	}

	pub fn set_id_no(&self,cur_id:ContainerId,cur_no:u32) {
		self.id_no.write(|id_no|{
			*id_no = (cur_id,cur_no)
		})
	}

	pub fn set_unique(&self) {
		
	}

	pub fn get_chunk_pointer<T:ChunkPointer>(&self) -> Option<T> {
		if self.id_no.is_init() {
			//println!("id");
			let (id,no) = self.id_no.read();
			Some(T::new(*id,*no,self.fp()))
		} else {
			None
		}
		//println!("");
	}

	pub fn id_no_pointer(&self) -> Arc<WriteOnce<(ContainerId,u32)>> {
		self.id_no.clone()
	}

	pub fn as_unique_len(&self) -> usize {
		self.data.len()
		+ std::mem::size_of::<ChunkLayout>()
	}

	pub fn as_delta_len(&self) -> usize {
		self.delta.read().as_ref().unwrap().len()
		+ std::mem::size_of::<ChunkLayout>()
		+ std::mem::size_of::<FpPointer>()
	}

	pub fn as_if_delta_len(&self,delta_len:usize) -> usize {
		delta_len
		+ std::mem::size_of::<ChunkLayout>()
		+ std::mem::size_of::<FpPointer>()
	}

	pub fn if_delta_max_len(&self) -> usize {
		let unique_len = self.as_unique_len();
		let zero_delta_len = self.as_if_delta_len(0);
		if unique_len <= zero_delta_len {
			0
		} else {
			unique_len - zero_delta_len
		}
	}

}

impl Deref for Chunk {
	type Target = [u8];
	fn deref(&self) -> &Self::Target {
		&self.data
	}
}

impl ContainerWriteItem for Chunk {
	fn len(&self) -> usize {
		match self.layout.read().kind() {
			ChunkKind::Unique => self.as_unique_len(),
			ChunkKind::Delta => self.as_delta_len(),
		}
	}
	
	fn data_len(&self) -> usize {
		self.data.len()
	}
	
	fn empty() -> &'static [u8] {
		CHUNK_LAYOUT_NONE.as_raw_data()
	}
	
	fn write_slice_to<'a>(&'a self,vec:&mut Vec<IoSlice<'a>>) {
		let layout = self.layout.read();
		match layout.kind() {
			ChunkKind::Unique => {
				vec.push(IoSlice::new(layout.as_raw_data()));
				vec.push(IoSlice::new(&self.data));
			}
			ChunkKind::Delta => {
				vec.push(IoSlice::new(layout.as_raw_data()));
				vec.push(IoSlice::new(self.base_pointer.read().as_ref().unwrap().as_raw_data()));
				vec.push(IoSlice::new(&self.delta.read().as_ref().unwrap()));
			}
		}
	}
}

impl DynContainerWriteItem for Chunk{
	type Meta = (ChunkKind,DefaultFingerprintType);
	#[inline(always)]
	fn to_metadata(&self) -> Self::Meta {
		let layout = self.layout.read();
		(layout.kind(),*layout.fp())
	}

	fn as_data(&self) -> &[u8] {
		&self.data
	}
}

